/* [n_bf]
 *********************
 * Things to remember:
 *
 * - detail is threadsafe, use caution, all changes will be seen by all threads; recommend: only use to .get data never .set
 * - global variables are global to all threads, use load & unload to initialize & free up memory
 * - use global isInit to initialize global variables requiring detail to be setup
 * - when using global isInit, the code must be mutex protected so all threads do not run the init, just one
 * - inter-thread coordination can be coordinated via the provided mutex
 */
#include <process.h>
#include <stdio.h>

#define DLL_EXPORT
#include "start.h"
#include "Database.h"
#include "PcManager.h"
#include "Exec.h"
#include "NetworkManager.h"

#include <iostream>
using namespace std;

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

#ifdef __cplusplus
extern "C" {
#endif	/*__cplusplus*/


// default global variables within dll
ParamManager *supported = NULL;
// module-specific global variables within dll
bool isInit = false;
Database *db = NULL;


// called after the module is loaded, useful to load global variables; Returns supported actions as a ParamManager*
DECLDIR ParamManager* load(void) {
	supported = new ParamManager();
	supported->setOverwrite(false);
	supported->set("action", "bf");
	supported->set("action", "bf_gen");
	supported->set("action", "bf_zip");

	return supported;
}


// called just before the module is unloaded, useful to unload global variables
DECLDIR void unload(void) {
	if(supported) {
		delete supported;
	}
	if(db) {
		delete db;
	}
}


void init(HANDLE mutex, ParamManager &detail) {
	MYSQL_RES *rUpdate;

	// ensure we only init once
	if(!isInit) {
		char query[DEFAULT_BUFLEN];

		// Instance Table
		db = new Database(
					detail.get("db_user"), detail.get("db_pass"),
					detail.get("db_name"),
					detail.get("db_host"), atoi(detail.get("db_port")));

/*		// take action db connection failed
		if(&(db->conn) == NULL) {
			return;
		}
*/
		// re-establish mysql connection if lost
		bool connect = true;
		mysql_options(&(db->conn), MYSQL_OPT_RECONNECT, &connect);

		sprintf(query, "CREATE TABLE IF NOT EXISTS n_wb_pc (");
		strcat(query, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat(query, ", style ENUM('pc', 'user')");
		strcat(query, ", name VARCHAR(256)");
		strcat(query, ", isHidden BOOL");
		strcat(query, ", dt_access DATETIME");
		strcat(query, ")");

		// thread-safe mysql call
		rUpdate = db->threadsafe_query(mutex, query);
		mysql_free_result(rUpdate);

		// do something
		isInit = true;
	}
}


char* dec2base(char *_orig, char *_base, char *_length);


void start(HANDLE mutex, ParamManager &detail, char *index) {
	// mutex protected init sequence
	WaitForSingleObject(mutex, INFINITE);	// Request ownership of mutex
	init(mutex, detail);				// Init sequence
	ReleaseMutex(mutex);					// Release mutex for other threads

/*	// take action db connection failed
	if(&(db->conn) == NULL) {
		printf("mysql_real_connect failed\n");
		// free up mysql thread-specific memory
		mysql_thread_end();
		return;
	}
*/

//	printf("bf: todo = '%s'\r\n", detail.get("todo"));
	if(!strcmp(detail.get("todo"), "bf_gen")) {
		// Purpose:
		// A single todo used to generate a new task
		BIGNUM *len = BN_new();
		BIGNUM *length = BN_new();
		BIGNUM *todo_count = BN_new();
		BN_CTX *ctx = BN_CTX_new();

		BN_add_word(len, strlen(detail.get("chars")));
		BN_dec2bn(&(length), detail.get("length"));
		
		BN_exp(todo_count, len, length, ctx);
		char *out = BN_bn2dec(todo_count);
		printf("bf_gen, todo_count = %s\n", out);


		// Now generate a new task with the provided details plus this todo_count
		NetworkManager nm;
		Connection *conn = nm.add();
		conn->settings.set("host", "127.0.0.1");
		conn->settings.set("port", "2244");
		nm.start(conn);

		conn->write("cmd=task_add\n");
		ParamManager add;
		while(true) {
			// wait forever until network activity
			nm.waitForReady();

			// process the network activity
			bool finished = false;
			if(nm.isData()) {
				while(conn = nm.getDataConnection()) {
					printf("recv: '%s'\n", conn->bm->getNext());
					if(strstr(conn->bm->getNext(), "cmd=task_add_reply")) {
						add.init(conn->bm->getNext(), "|");
						conn->bm->pop();
						finished = true;
						break;
					}
					conn->bm->pop();
				}
				if(finished) {
					break;
				}
			}
		}

		char data[DEFAULT_BUFLEN];
		int task_id = atoi(add.get("id"));

		sprintf(data, "cmd=task_set|id=%d|data=todo_count|value=%s\n", task_id, out);							conn->write(data);
		OPENSSL_free(out);

		sprintf(data, "cmd=task_set|id=%d|data=block_size|value=%s\n", task_id, detail.get("block_size"));		conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=timeout|value=%s\n", task_id, detail.get("timeout"));			conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=db_user|value=%s\n", task_id, detail.get("db_user"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_pass|value=%s\n", task_id, detail.get("db_pass"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_name|value=%s\n", task_id, detail.get("db_name"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_host|value=%s\n", task_id, detail.get("db_host"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_port|value=%s\n", task_id, detail.get("db_port"));			conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=todo|value=bf\n", task_id);										conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=chars|value=%s\n", task_id, detail.get("chars"));				conn->write(data);

		out = BN_bn2dec(length);
		sprintf(data, "cmd=task_set|id=%d|data=length|value=%s\n", task_id, out);								conn->write(data);
		OPENSSL_free(out);

		sprintf(data, "cmd=task_start|id=%d\n", task_id);														conn->write(data);

		BN_CTX_free(ctx);
		BN_free(todo_count);
		BN_free(length);
		BN_free(len);
	}
	// BF, to just demo generating the permutations
	else if(!strcmp(detail.get("todo"), "bf")) {
		if(detail.isSet("chars")) {
			char num_chars[DEFAULT_BUFLEN];
			_itoa(strlen(detail.get("chars")), num_chars, 10);
			char *output = dec2base(index, num_chars, detail.get("length"));
			char *teststr = new char[atoi(detail.get("length")) + 1];
			memset(teststr, 0, atoi(detail.get("length")) + 1);
			for(int index = 0; index < atoi(detail.get("length")); index ++) {
				teststr[index] = detail.get("chars")[output[index]];
			}
			_strrev(teststr);
//			printf("teststr: '%s'\n", teststr);

			char cmd[DEFAULT_BUFLEN];
			sprintf(cmd, ".\\nodes\\n_bf\\test.cmd %s", teststr);
			Exec proc(cmd);
/*			if(proc.isOutput()) {
				// attempt succeeded, return code from the attempt was 0
				sprintf(cmd, ".\\nodes\\n_bf\\found.cmd %s", teststr);
				Exec proc2(cmd);
			}
*/
//			else {
//				// attempt failed, return code from the attempt was not 0
//			}

			delete[] teststr;
			delete[] output;
		}
	}
	// BF_ZIP, same as BF but actually performs a test
	else if(!strcmp(detail.get("todo"), "bf_zip")) {
		if(detail.isSet("chars")) {
			char num_chars[DEFAULT_BUFLEN];
			_itoa(strlen(detail.get("chars")), num_chars, 10);
			char *output = dec2base(index, num_chars, detail.get("length"));
			char *teststr = new char[atoi(detail.get("length")) + 1];
			memset(teststr, 0, atoi(detail.get("length")) + 1);
			for(int index = 0; index < atoi(detail.get("length")); index ++) {
				teststr[index] = detail.get("chars")[output[index]];
			}
			_strrev(teststr);
//			printf("teststr: '%s'\n", teststr);

			char cmd[DEFAULT_BUFLEN];
			sprintf(cmd, ".\\bf\\ziptest.cmd %s", teststr);
			Exec proc(cmd);
			if(proc.isOutput()) {
				// attempt succeeded, return code from the attempt was 0
				sprintf(cmd, ".\\bf\\zipfound.cmd %s", teststr);
				Exec proc2(cmd);
			}
//			else {
//				// attempt failed, return code from the attempt was not 0
//			}

			delete[] teststr;
			delete[] output;
		}
	}

	// free up mysql thread-specific memory
	mysql_thread_end();
}


// Using BIGNUMs convert a decimal of any size to the specified base
char* dec2base(char *_orig, char *_base, char *_length) {
	char *output = new char[atoi(_length) + 1];
	memset(output, 0, atoi(_length) + 1);
	int index = 0;

	BIGNUM *orig = BN_new();
	BIGNUM *base = BN_new();
	BN_CTX *ctx = BN_CTX_new();

	BN_dec2bn(&orig, _orig);
	BN_dec2bn(&base, _base);

	BIGNUM *result = BN_new();
	BIGNUM *rem = BN_new();

	// while orig > base
	BN_div(result, rem, orig, base, ctx);

	char *tmp = BN_bn2dec(rem);
//	printf("REM: %s", tmp);
	output[index ++]  = atoi(tmp);
	OPENSSL_free(tmp);

	while(!BN_is_zero(result)) {
		BN_div(result, rem, result, base, ctx);

		char *tmp = BN_bn2dec(rem);
//		printf(" %s", tmp);
		output[index ++]  = atoi(tmp);
		OPENSSL_free(tmp);
	}
//	printf("\n");

	BN_free(rem);
	BN_free(result);
	BN_CTX_free(ctx);
	BN_free(base);
	BN_free(orig);

	return output;
}


#ifdef __cplusplus
}
#endif	/*__cplusplus */